<?php
/**
 * 程序入口类
 * ver 1.2
 * author Myxf
 */
require_once "config.php";
if(!defined('APP_VER')) exit("die!");
session_start();

//主框架类
class App extends minConfig
{   
    private $smarty = "";
    
    function __construct(){
        set_error_handler(array($this, 'handleError'));
        register_shutdown_function(array($this, 'handleFatalError'));
        $_smarty = $this->mClass("app_smarty",array($this->minConfig['View']));
        $this->smarty = $_smarty->getSmarty();
    }

    //自动注册模板引擎变量
    public function __set($name, $value){
        $this->smarty->assign(array($name=>$value));
        $this->$name = $value;
    }

    //运行框架
    public function appRun(){
        if (!$this->minConfig['Config']['debug']) error_reporting(0);
        $this->path_info();
        $ClassName = empty($_REQUEST['c']) ? "main" : $this->mArgs("c");
        $Action =  empty($_REQUEST['a']) ? "index" : $this->mArgs("a");
        $Class = $this->mClass($ClassName,"",Controller_Path);
        if (!method_exists($Class,$Action)) $this->ErrMsg($ClassName."->".$Action."()函数不存在，请检查。","",404);
        echo $Class->$Action();
    }

    //pathinfo处理
    public function path_info(){
        if(!empty($_SERVER['PATH_INFO'])){
            $url_args = explode("/", $_SERVER['PATH_INFO']);$url_sort = array();
            for($u = 1; $u < count($url_args); $u++){
                if($u == 1)$url_sort["c"] = $url_args[$u];
                elseif($u == 2)$url_sort["a"] = $url_args[$u];
                else {$url_sort[$url_args[$u]] = isset($url_args[$u+1]) ? $url_args[$u+1] : "";$u+=1;}}
            if("POST" == strtoupper($_SERVER['REQUEST_METHOD'])){$_REQUEST = $_POST =  $_POST + $url_sort;
            }else{$_REQUEST = $_GET = $_GET + $url_sort;}
        }
    }

    //调用Model
    public function mModel($ClassName, $Args = null,$Path = "") {
        $Path = !empty($Path) ? "/Modules/".$Path : "";
        return $this->mClass($ClassName,$Args,APP_PATH.$Path.'/Model/');
    }

    //载入class类
    public function mClass($ClassName, $Args = null, $Path = Ext_Path) {
        // 检查类名称是否正确，以保证类定义文件载入的安全性
        if(preg_match('/[^a-z0-9\-_.]/i', $ClassName)) $this->ErrMsg($ClassName."类名称错误，请检查。","",404);
        $Path = $Path.$ClassName.".php";
        if (!file_exists($Path))$this->ErrMsg($ClassName."类未找到，请检查。","",404);
        require_once $Path;
        // 检查类定义是否存在
        if (!class_exists($ClassName, false))$this->ErrMsg($ClassName."类定义不存在，请检查。","",404);
        $argString = '';$comma = ''; 
        if(null != $Args)for ($i = 0; $i < count($Args); $i ++) { $argString .= $comma . "\$Args[$i]"; $comma = ', '; } 
        eval("\$".$ClassName." = new ".$ClassName."(".$argString.");"); 
        return $$ClassName;
    }

    //渲染模板引擎
    public function display($tpl){
        $this->smarty->display($tpl);
    }

    //数据接收
    public function mArgs($Name = null, $Default = False, $Clear = False, $Method = null){
        $Args =  new Args();
        return $Args->__input($Name,$Default,$Clear,$Method);
    }

    //捕获错误
    public function handleError($Code, $Message, $File, $Line)
    {
       $GLOBALS['APP_Message'] = $Message."<br>位置：".$File."&nbsp;&nbsp;第".$Line."行";
       return true;
    }

    //执行完毕后调用
    public function handleFatalError(){
        if (!empty($GLOBALS['APP_Message']) && $this->minConfig['Config']['debug']) $this->ErrMsg();
    }

    //错误信息
    public function ErrMsg($Content = "",$Url = "javascript:void(history.go(-1))",$code = 500){
        if ($code == 500){
            header('HTTP/1.1 500 Internal Server Error');
            $Content = $this->minConfig['Config']['debug'] ? $GLOBALS['APP_Message']."<br>".$Content : "服务器内部错误，请联系管理员。";
        }
        if ($code == 404){
            header('HTTP/1.1 404 Not Found');
            $Content = $this->minConfig['Config']['debug'] ? $GLOBALS['APP_Message']."<br>".$Content : "404 Not Found";
        }
        $GLOBALS['APP_Message'] = "";
        $html = '<html><head><meta charset="UTF-8"><title>错误提示!</title><meta name="robots" content="noindex" /><style type="text/css"><!--body {color: #444444;background-color: #EEEEEE;font-family: Trebuchet MS, sans-serif;font-size: 80%;}h2 { font-size: 1.2em; }#page{background-color: #FFFFFF;width: 90%;margin: 24px auto;padding: 12px;}#header {padding: 6px ;text-align: center;}.status4xx { background-color: #C55042; color: #FFFFFF; }#content {padding: 4px 0 24px 0;}#footer {color: #666666;background: #f9f9f9;padding: 10px 20px;border-top: 5px #efefef solid;font-size: 0.8em;text-align: center;}#footer a {color: #999999;}--></style></head><body><div id="page"><div id="header" class="status4xx"><h1>错误提示!</h1></div><div id="content"><h2>提示内容:</h2><p>[content]</p><P>请我点击<a href="[url]">【返回】</a>上一页.</p></div><div id="footer"><p>Powered by '.$this->minConfig['Config']['Name'].'</p></div></div></body></html>';
        $html = str_replace("[content]", $Content, $html);
        $html = str_replace("[url]", $Url, $html);
        exit($html);
    }

    //提示信息
    public function mAlert($Content = "",$Title = "提示信息!",$Url = "javascript:void(history.go(-1))",$Success = True){
        $status = $Success ? "2xx" : "4xx";
        $Title = empty($Title) ? "提示信息!" : $Title;
        $Url = empty($Url) ? "javascript:void(history.go(-1))" : $Url;
        $html = '<html><head><meta charset="UTF-8"><title>'.$Title.'</title><meta name="robots" content="noindex" /><style type="text/css"><!--body {color: #444444;background-color: #EEEEEE;font-family: Trebuchet MS, sans-serif;font-size: 80%;}h2 { font-size: 1.2em; }#page{background-color: #FFFFFF;width: 90%;margin: 24px auto;padding: 12px;}#header {padding: 6px ;text-align: center;}.status2xx { background-color: #006600; color: #FFFFFF; }.status4xx { background-color: #C55042; color: #FFFFFF; }#content {padding: 4px 0 24px 0;}#footer {color: #666666;background: #f9f9f9;padding: 10px 20px;border-top: 5px #efefef solid;font-size: 0.8em;text-align: center;}#footer a {color: #999999;}--></style></head><body><div id="page"><div id="header" class="status'.$status.'"><h1>'.$Title.'</h1></div><div id="content"><h2>提示内容:</h2><p>[content]</p><P>请我点击<a href="[url]">【返回】</a>上一页.</p></div><div id="footer"><p>Powered by '.$this->minConfig['Config']['Name'].'</p></div></div></body></html>';
        $html = str_replace("[content]", $Content, $html);
        $html = str_replace("[url]", $Url, $html);
        exit($html);
    }

    //Access 文件缓存
    public function access($name, $value = NULL, $life_time = -1){
        // 准备缓存目录和缓存文件名称，缓存文件名称为$name的MD5值，文件后缀为php
        $alias = "";
        if (strpos($name, "_")!==false){
            $va = explode("_", $name);
            $alias = $va[0]."_";
            $name = str_replace($alias, "", $name);
        }
        $sfile = $this->minConfig['View']['tmp_path'].$alias.md5($name).".php";
        if($name!='' && $value!=''){ 
            $life_time = ( -1 == $life_time ) ? '300000000' : $life_time;
            $value = '<?php die();?>'.( time() + $life_time ).serialize($value); // 数据被序列化后保存
            return file_put_contents($sfile, $value);
        }elseif($name!='' && $value===''){ 
            return @unlink($sfile);
        }else{
            if( !is_readable($sfile) )return FALSE;
            $arg_data = file_get_contents($sfile);
            if( substr($arg_data, 14, 10) < time() ){
                @unlink($sfile); // 过期则移除缓存文件，返回FALSE
                return FALSE;
            }
            return unserialize(substr($arg_data, 24)); // 数据反序列化后返回
        }
    }
}

//数据接收类
class Args {
    private $args = null;

    public function __construct(){
        $this->args = $_REQUEST;
    }
    
    public function get($name = null, $default = FALSE, $clear = FALSE, $method = null){
        if(null != $name){
            if( $this->has($name) ){
                if( null != $method ){
                    switch (strtolower($method)) {
                        case 'get':
                            if ($clear){
                                return $this->clearLabel($this->filter($_GET[$name]));
                            }else{
                                return $this->filter($_GET[$name]);
                            }
                        case 'post':
                            if ($clear){
                                return $this->clearLabel($this->filter($_POST[$name]));
                            }else{
                                return $this->filter($_POST[$name]);
                            }
                        case 'cookie':
                            if ($clear){
                                return $this->clearLabel($this->filter($_COOKIE[$name]));
                            }else{
                                return $this->filter($_COOKIE[$name]);
                            }
                    }
                }
                if ($clear){
                    return $this->clearLabel($this->filter($this->args[$name]));
                }else{
                    return $this->filter($this->args[$name]);
                }
                
            }else{
                return (FALSE === $default) ? FALSE : $default;
            }
        }else{
            return $this->args;
        }
    }

    public function has($name){
        return isset($this->args[$name]);
    }

    public function __input($name = null, $default = FALSE, $clear = FALSE, $method = null){
        return $this->get($name, $default, $clear, $method);
    }
    
    public function request(){
        return $this->filter($_SERVER["QUERY_STRING"]);
    }

    public function clearLabel($html){
        $search = array ("'<script[^>]*?>.*?</script>'si", "'<[/!]*?[^<>]*?>'si", "'([rn])[s]+'", "'&(quot|#34);'i", "'&(amp|#38);'i", "'&(lt|#60);'i", "'&(gt|#62);'i", "'&(nbsp|#160);'i", "'&(iexcl|#161);'i", "'&(cent|#162);'i", "'&(pound|#163);'i", "'&(copy|#169);'i", "'&#(d+);'");
        $replace = array ("", "", "\1", "\"", "&", "<", ">", " ", chr(161), chr(162), chr(163), chr(169), "chr(\1)");
        return preg_replace($search, $replace, $html);
    }

    //安全过滤
    public function filter($string, $force = 1, $allow='') {
        if($force) {
            if(is_array($string)) {
                foreach ($string as $key => $val) {
                    $string[$key] = $this->filter($val, $force, $allow);
                }
            } else {
                $string = $this->remove_xss($string, $allow);
                $string = addslashes($string);
            }
        } 
        return $string;
    }

    public function remove_xss($content,$allow='') {
        $danger = 'javascript,vbscript,expression,applet,meta,xml,blink,link,style,script,embed,object,frame,frameset,ilayer,layer,bgsound,title,base';
        $event = 'onabort|onactivate|onafterprint|onafterupdate|onbeforeactivate|onbeforecopy|onbeforecut|onbeforedeactivate|onbeforeeditfocus|'.
        'onbeforepaste|onbeforeprint|onbeforeunload|onbeforeupdate|onblur|onbounce|oncellchange|onchange|onclick|oncontextmenu|oncontrolselect|'.
        'oncopy|oncut|ondataavailable|ondatasetchanged|ondatasetcomplete|ondblclick|ondeactivate|ondrag|ondragend|ondragenter|ondragleave|'.
        'ondragover|ondragstart|ondrop|onerror|onerrorupdate|onfilterchange|onfinish|onfocus|onfocusin|onfocusout|onhelp|onkeydown|onkeypress|'.
        'onkeyup|onlayoutcomplete|onload|onlosecapture|onmousedown|onmouseenter|onmouseleave|onmousemove|onmouseout|onmouseover|onmouseup|'.
        'onmousewheel|onmove|onmoveend|onmovestart|onpaste|onpropertychange|onreadystatechange|onreset|onresize|onresizeend|onresizestart|'.
        'onrowenter|onrowexit|onrowsdelete|onrowsinserted|onscroll|onselect|onselectionchange|onselectstart|onstart|onstop|onsubmit|onunload';
        if(!empty($allow)) {
            $allows = explode(',',$allow);
            $danger = str_replace($allow,'',$danger);
        }
        $danger = str_replace(',','|',$danger);
        //替换所有危险标签
        $content = preg_replace("/<\s*($danger)[^>]*>[^<]*(<\s*\/\s*\\1\s*>)?/is",'',$content);
        //替换所有危险的JS事件
        $content = preg_replace("/<([^>]*)($event)\s*\=([^>]*)>/is","<\\1 \\3>",$content);
        //替换违禁词
        //$content = $this->keyword($content);
        return $content;
    }
}

//数据库操作类
class mModel extends App
{
    public $mysqli = "";
    public $debug = false;
    public $prefix = "";

    function __construct(){
        $this->debug = $this->minConfig['Config']['debug'];
        $conn = array($this->minConfig['Db']);
        $this->mysqli = $this->mClass("app_mysqli",$conn);
        $this->prefix = $this->minConfig['Db']["prefix"];
    }

    public function findSql($sql){
        return $this->mysqli->getArray($sql);
    }

    public function edits($sql){
        return $this->mysqli->execs($sql);
    }

    public function create($sql,$table){
        if( FALSE != $this->mysqli->exec($sql) ){
            if( $newinserid = $this->mysqli->newinsertid()) return $newinserid;
            return array_pop( $this->findSql("Select id from ".$table." order by id desc"));
        }
        return FALSE;
    }

    public function mysqliclose(){
        return @mysqli_close($this->mysqli->conn);
    }
}
